using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;
using Atomic.Structures;
using Atomic.Thermodynamics.Electrons;

namespace Atomic.Vasp
{
	/// <summary>
	/// Parsing of VASP4 output and output lacking vasprun.xml. This class uses a different serialization scheme.
	/// </summary>
	[Serializable]
	public class SimpleVaspResult : IVaspResult
	{
		public SimpleVaspResult(DirectoryInfo directory)
		{
			// Read only basic position and energy information.

			Lattice primitiveLattice;
			int primitiveCells;
			IEnumerable<Atom> atoms;

			ReadOutcar(directory, out primitiveLattice, out primitiveCells, out atoms);
			BuildStructure(directory, primitiveLattice, primitiveCells, atoms);
			ReadOszicar(directory);
			ReadPoscar(directory, atoms);
			ReadElectronicStateDensity(directory);
		}

		private void BuildStructure(DirectoryInfo directory, Lattice primitiveLattice, int primitiveCells, IEnumerable<Atom> atoms)
		{
			// Higher precision in CONTCAR.
			Structure structure;
			using (StreamReader reader = new StreamReader(directory.AddFile("CONTCAR").FullName))
			{
				structure = VaspPoscar.Read(reader, atoms);
			}

			// Try parsing the primitive structure from the primitive basis.
			Structure primitiveStructure = BuildPrimitiveStructure(structure, primitiveLattice, primitiveCells);

			FinalStructure = new VaspStructure(structure.Basis, structure.Lattice, structure.Sites, primitiveStructure);
		}

		private Structure BuildPrimitiveStructure(Structure structure, Lattice primitiveLattice, int primitiveCells)
		{
			if (primitiveCells == 1)
			{
				// Use exactly same basis.
				primitiveLattice = structure.Lattice;
			}

			if (primitiveCells > 0 && primitiveLattice != null)
			{
				// Tolerance of identical positions in physical distance.
				double tol = 1.0e-4;

				//if (primitiveBasis == null)
				//{
				//	// Couldn't determine the basis. Just ignore it for now.
				//	primitiveBasis = Basis.StandardBasis;
				//}

				//Lattice primitiveLattice = primitiveBasis;
				//Lattice lattice = structure.Lattice;

				List<Site> primitiveSites = new List<Site>();

				foreach (Site site in structure.Sites)
				{
					Atom atom = site.Atom;

					// Real physical position.
					//Vector3 p = lattice.Map * site.Position;
					SpaceVector p = site.Position.PhysicalCoordinates;

					bool found = false;
					foreach (Site site0 in primitiveSites)
					{
						if (site0.Atom != atom)
						{
							continue;
						}

						// Physical position of other atom.
						//Vector3 p0 = lattice.Map * site0.Position;
						SpaceVector p0 = site0.Position.PhysicalCoordinates;

						// Difference relative to the primitive lattice vectors (integer number if identical positions).
						SpaceVector d = primitiveLattice.Decompose(new Position(p - p0)).Coordinates;

						// If the distance in real physical position is below this, then the site is considered the same.
						if (SpaceVector.Norm(primitiveLattice.PhysicalMap * new SpaceVector(d[0] - Math.Round(d[0]), d[1] - Math.Round(d[1]), d[2] - Math.Round(d[2]))) < tol)
						{
							found = true;
							break;
						}
					}

					if (!found)
					{
						primitiveSites.Add(site);
					}
				}

				// Silently ignore discrepancy (don't want to fail if not used), but set to null; only define primitive structure if it matches.
				if (primitiveSites.Count * primitiveCells == structure.Sites.Count)
				{
					return new Structure(primitiveLattice, primitiveSites);
				}
			}

			// Failed. Just return a null reference.
			return null;
		}

		private void ReadPoscar(DirectoryInfo directory, IEnumerable<Atom> atoms)
		{
			FileInfo file = directory.AddFile("POSCAR");

			if (!file.Exists || file.Length == 0)
			{
				throw new FileNotFoundException();
			}

			using (StreamReader reader = file.OpenText())
			{
				InitialStructure = VaspPoscar.Read(reader, atoms);
			}
		}
	
		private void ReadOszicar(DirectoryInfo directory)
		{
			FileInfo file = directory.AddFile("OSZICAR");

			if (!file.Exists || file.Length == 0)
			{
				throw new FileNotFoundException();
			}

			string line = null;
			using (StreamReader reader = file.OpenText())
			{
				string s;
				while ((s = reader.ReadLine()) != null)
				{
					if (s.Contains("Linear response reoptimize wavefunctions"))
					{
						// Linear response problem. Skip from here on.
						break;
					}

					// Use last instance of the "E0=" expression.
					line = s;
				}
			}

			Energy = double.Parse(line.Substring(line.IndexOf("E0=") + 3).TrimStart(' ').Split(' ')[0], CultureInfo.InvariantCulture);

			// Read the magnetic moment from OSZICAR, as it doesn't seem to be listed in vasprun.xml.
			if (line.Contains("mag="))
			{
				MagneticMoment = double.Parse(line.Substring(line.IndexOf("mag=") + 4).TrimStart(' ').Split(' ')[0], CultureInfo.InvariantCulture);
			}
			else
			{
				// Not spin-polarized calculation.
				MagneticMoment = 0.0;
			}
		}

		private void ReadOutcar(DirectoryInfo directory, out Lattice primitiveLattice, out int primitiveCells, out IEnumerable<Atom> atoms)
		{
			primitiveLattice = null;
			primitiveCells = 0;
			FermiEnergy = double.NaN;
			Electrons = double.NaN;
			ExternalPressure = double.NaN;

			// Atoms as determined by entries in OUTCAR. May be missing in POSCAR/CONTCAR.
			atoms = new List<Atom>();

			FileInfo file = directory.AddFile("OUTCAR");

			if (!file.Exists || file.Length == 0)
			{
				throw new FileNotFoundException();
			}

			List<string> lines = new List<string>();
			using (StreamReader reader = file.OpenText())
			{
				string s;
				while ((s = reader.ReadLine()) != null)
				{
					lines.Add(s);
				}
			}

			for (int i = 0; i < lines.Count; i++)
			{
				string line = lines[i];

				if (line.StartsWith(" POTCAR:"))
				{
					string symbol = line.Replace(" POTCAR:", "").Trim().Split(' ')[1];
					Atom atom = Atom.Parse(symbol);
					if (!((List<Atom>)atoms).Contains(atom))
					{
						((List<Atom>)atoms).Add(atom);
					}
				}

				if (line.Contains("E-fermi"))
				{
					FermiEnergy = double.Parse(line.Split(':')[1].Trim().Split(' ')[0], CultureInfo.InvariantCulture);
				}

				if (line.Contains("NELECT ="))
				{
					Electrons = double.Parse(line.Split('=')[1].Trim().Split(' ')[0], CultureInfo.InvariantCulture);
				}

				if (line.Contains("Analysis of symmetry for initial positions (statically)"))
				{
					for (int j = i + 2; j < lines.Count; j++)
					{
						if (lines[j].StartsWith("==="))
						{
							// Next section.
							break;
						}

						if (lines[j].Contains("A1 ="))
						{
							// Extract lattice vectors of the primitive cell.
							SpaceVector[] a = new SpaceVector[3];
							for (int k = 0; k < 3; k++)
							{
								// Assuming format: "A1 = (   2.3216904700,  -4.0212858535,   0.0000000000)"
								string s = lines[j + k];
								a[k] = AtomicEnvironment.ParseSpaceVector(s.Substring(s.IndexOf("(") + 1, s.IndexOf(")") - s.IndexOf("(") - 1));
							}
							primitiveLattice = Lattice.Parse(a[0], a[1], a[2]);
						}

						if (lines[j].Contains("primitive cells build up your supercell"))
						{
							primitiveCells = int.Parse(lines[j].Trim().Split(' ')[0]);
						}

						if (lines[j].Contains("Original cell was already a primitive cell"))
						{
							primitiveCells = 1;
						}
					}
				}

				if (line.Contains("external pressure ="))
				{
					// Expecting unit kbar (kB). Replace with separator. Convert to eV/Å^3.
					ExternalPressure = double.Parse(line.Replace("kB", "=").Split('=')[1].Trim(), CultureInfo.InvariantCulture) / 1602.176487;
				}
			}
		}

		/*private void ReadOutcar(DirectoryInfo directory, out Lattice primitiveLattice, out int primitiveCells, out IEnumerable<Atom> atoms, out double externalPressure)
		{
			// Atoms as determined by entries in OUTCAR. May be missing in POSCAR/CONTCAR.
			List<string> potcar = new List<string>();
			atoms = new List<Atom>();

			primitiveLattice = null;
			primitiveCells = 0;
			externalPressure = double.NaN;

			List<string> lines = new List<string>();
			using (StreamReader reader = new StreamReader(directory.AddFile("OUTCAR").FullName))
			{
				string s;
				while ((s = reader.ReadLine()) != null)
				{
					lines.Add(s);
				}
			}

			for (int i = 0; i < lines.Count; i++)
			{
				string line = lines[i];

				if (line.StartsWith(" POTCAR:") && !potcar.Contains(line))
				{
					potcar.Add(line);
				}

				if (line.Contains("E-fermi"))
				{
					FermiEnergy = double.Parse(line.Split(':')[1].Trim().Split(' ')[0], CultureInfo.InvariantCulture);
				}

				if (line.Contains("Analysis of symmetry for initial positions (statically)"))
				{
					for (int j = i + 2; j < lines.Count; j++)
					{
						if (lines[j].StartsWith("==="))
						{
							// Next section.
							break;
						}

						if (lines[j].Contains("A1 ="))
						{
							// Extract lattice vectors of the primitive cell.
							SpaceVector[] a = new SpaceVector[3];
							for (int k = 0; k < 3; k++)
							{
								// Assuming format: "A1 = (   2.3216904700,  -4.0212858535,   0.0000000000)"
								string s = lines[j + k];
								a[k] = AtomicEnvironment.ParseSpaceVector(s.Substring(s.IndexOf("(") + 1, s.IndexOf(")") - s.IndexOf("(") - 1));
							}
							primitiveLattice = Lattice.Parse(a[0], a[1], a[2]);
						}

						if (lines[j].Contains("primitive cells build up your supercell"))
						{
							primitiveCells = int.Parse(lines[j].Trim().Split(' ')[0]);
						}

						if (lines[j].Contains("Original cell was already a primitive cell"))
						{
							primitiveCells = 1;
						}
					}
				}

				if (line.Contains("external pressure ="))
				{
					// Expecting unit kbar (kB). Replace with separator. Convert to eV/Å^3.
					externalPressure = double.Parse(line.Replace("kB", "=").Split('=')[1].Trim(), CultureInfo.InvariantCulture) / 1602.176487;
				}
			}

			foreach (string line in potcar)
			{
				string symbol = line.Replace(" POTCAR:", "").Trim().Split(' ')[1];
				((List<Atom>)atoms).Add(Atom.Parse(symbol));
			}
		}*/

		private void ReadElectronicStateDensity(DirectoryInfo directory)
		{
			if (!directory.AddFile("DOSCAR").Exists || directory.AddFile("DOSCAR").Length == 0)
			{
				// Allow this to be undefined.
				return;
			}

			using (StreamReader reader = directory.AddFile("DOSCAR").OpenText())
			{
				//ElectronicStateDensity = VaspElectronicStateDensity.Parse(FermiEnergy, FinalStructure.Sites.Count, reader);
				ElectronicStateDensity = VaspElectronicStateDensity.Parse(FermiEnergy, Electrons, reader);
			}
		}

		/// <summary>
		/// Energy per unit cell.
		/// </summary>
		public double Energy
		{
			get;
			private set;
		}

		/*/// <summary>
		/// Potential energy per atom.
		/// </summary>
		public double EnergyPerAtom
		{
			get
			{
				return Energy / FinalStructure.Sites.Count;
			}
		}*/

		/// <summary>
		/// Magnetic moment per unit cell.
		/// </summary>
		public double MagneticMoment
		{
			get;
			private set;
		}

		/*/// <summary>
		/// Magnetic moment per atom.
		/// </summary>
		public double MagneticMomentPerAtom
		{
			get
			{
				return MagneticMoment / FinalStructure.Sites.Count;
			}
		}*/
		
		public double FermiEnergy
		{
			get;
			private set;
		}
		
		public double Electrons
		{
			get;
			private set;
		}

		/// <summary>
		/// External pressure in eV/Å^3. Multiply by 1602.176487 to get this value in kbar (as VASP reports).
		/// in kPa.
		/// </summary>
		public double ExternalPressure
		{
			get;
			private set;
		}

		/// <summary>
		/// Initial structure before relaxation.
		/// </summary>
		public Structure InitialStructure
		{
			get;
			private set;
		}

		/// <summary>
		/// Final structure after relaxation.
		/// </summary>
		public VaspStructure FinalStructure
		{
			get;
			private set;
		}

		/// <summary>
		/// Electronic density of states (DOSCAR file).
		/// </summary>
		public ElectronicStateDensity ElectronicStateDensity
		{
			get;
			private set;
		}
	}
}
